package com.yeskery.nut.scan;

import com.yeskery.nut.annotation.aop.Aspect;
import com.yeskery.nut.annotation.bean.*;
import com.yeskery.nut.annotation.environment.ConfigurationProperties;
import com.yeskery.nut.annotation.web.Controller;
import com.yeskery.nut.annotation.web.RestController;
import com.yeskery.nut.application.NutApplication;
import com.yeskery.nut.bean.*;
import com.yeskery.nut.core.NutException;
import com.yeskery.nut.util.BeanUtils;
import com.yeskery.nut.util.ClassUtils;
import com.yeskery.nut.util.ReflectUtils;
import com.yeskery.nut.util.StringUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * 默认的注解Class扫描器
 * @author sprout
 * 2022-06-14 18:31
 */
public class DefaultAnnotationClassScanner implements AnnotationClassScanner {

    /** 日志对象 */
    private static final Logger logger = Logger.getLogger(DefaultAnnotationClassScanner.class.getName());

    /** 注解处理器map */
    private final Map<Class<? extends Annotation>, AnnotationHandler> annotationHandlerMap = new LinkedHashMap<>();

    /** Nut应用对象 */
    private final NutApplication nutApplication;

    /** 扫描的基础路径 */
    private final Set<String> basePackages;

    /** Bean扫描元数据集合 */
    private Collection<BeanAnnotationScanMetadata> beanAnnotationScanMetadataCollection;

    /**
     * 构建默认的注解Class扫描器
     * @param nutApplication Nut应用对象
     * @param basePackages 扫描的基础路径
     */
    public DefaultAnnotationClassScanner(NutApplication nutApplication, Set<String> basePackages) {
        this.nutApplication = nutApplication;
        this.basePackages = basePackages;
    }

    @Override
    public void registerAnnotationHandler(Class<? extends Annotation> clazz, AnnotationHandler annotationHandler) {
        if (annotationHandlerMap.containsKey(clazz)) {
            throw new NutException("Annotation [" + clazz.getName() + "] Handler Already Exists.");
        }
        annotationHandlerMap.put(clazz, annotationHandler);
    }

    @Override
    public Collection<Void> scan() {
        Map<Class<? extends Annotation>, Collection<Class<?>>> annotationClassMap = new HashMap<>(annotationHandlerMap.size());
        for (Map.Entry<Class<? extends Annotation>, AnnotationHandler> entry : annotationHandlerMap.entrySet()) {
            annotationClassMap.put(entry.getKey(), new ArrayList<>());
        }

        for (String basePackage : basePackages) {
            ClassUtils.obtainClassListByFilter(basePackage,
                    cls -> isValidClass(cls) ? annotationHandlerMap.keySet().stream()
                            .filter(c -> BeanUtils.isExistSpecifiedAnnotation(cls, c))
                            .findFirst()
                            .orElse(null) : null,
                    (cls, clazz) -> annotationClassMap.get(clazz));
        }

        // 执行bean定义后置处理方法
        doExecuteBeanDefinitionPostProcess(annotationHandlerMap, annotationClassMap);

        Collection<BeanAnnotationScanMetadata> beanAnnotationScanMetadataCollection = getBeanAnnotationScanMetadataCollection(annotationClassMap);
        ((BeanRegister) nutApplication.getApplicationContext()).setBeanAnnotationScanMetadata(beanAnnotationScanMetadataCollection);

        Map<Class<? extends Annotation>, Collection<Class<?>>> beanRegisterAnnotationClassMap = new HashMap<>(16);
        Map<Class<? extends Annotation>, Collection<Class<?>>> otherAnnotationClassMap = new HashMap<>(16);
        for (Map.Entry<Class<? extends Annotation>, Collection<Class<?>>> entry : annotationClassMap.entrySet()) {
            if (isBeanRegisterAnnotation(entry.getKey())) {
                beanRegisterAnnotationClassMap.put(entry.getKey(), entry.getValue());
            } else {
                otherAnnotationClassMap.put(entry.getKey(), entry.getValue());
            }
        }

        doHandleAnnotationClass(beanRegisterAnnotationClassMap, beanAnnotationScanMetadataCollection);
        doHandleAnnotationClass(otherAnnotationClassMap, beanAnnotationScanMetadataCollection);
        return Collections.emptySet();
    }

    /**
     * 获取Nut应用对象
     * @return Nut应用对象
     */
    protected NutApplication getNutApplication() {
        return nutApplication;
    }

    /**
     * 执行bean注册初始化后的扩展方法
     */
    protected void doAfterAnnotationBeanRegisterInitialize() {
        BaseApplicationContext applicationContext = (BaseApplicationContext) getNutApplication().getApplicationContext();
        ImportAnnotationMetadata importAnnotationMetadata = applicationContext.getBean(ImportAnnotationMetadata.class);
        for (ImportBeanRegistrar importBeanRegistrar : applicationContext.getBeans(ImportBeanRegistrar.class)) {
            try {
                importBeanRegistrar.registerBeans(importAnnotationMetadata, applicationContext);
            } catch (Exception e) {
                logger.logp(Level.SEVERE, ImportBeanRegistrar.class.getName(), "registerBeans",
                        "ImportBeanRegistrar Process Fail, Type [" + ImportBeanRegistrar.class.getName()
                                + "] Class [" + importBeanRegistrar.getClass().getName() + "].", e);
            }
        }

        for (ImportSelector importSelector : applicationContext.getBeans(ImportSelector.class)) {
            try {
                for (String className : importSelector.selectImports(importAnnotationMetadata)) {
                    Class<?> clazz = ClassUtils.getClassByName(className);
                    if (clazz != null) {
                        applicationContext.registerBean(clazz);
                    }
                }
            } catch (Exception e) {
                logger.logp(Level.SEVERE, ImportSelector.class.getName(), "selectImports",
                        "ImportSelector Process Fail, Type [" + ImportSelector.class.getName()
                                + "] Class [" + importSelector.getClass().getName() + "].", e);
            }
            try {
                for (Class<?> clazz : importSelector.selectImportClasses(importAnnotationMetadata)) {
                    applicationContext.registerBean(clazz);
                }
            } catch (Exception e) {
                logger.logp(Level.SEVERE, ImportSelector.class.getName(), "selectImportClasses",
                        "ImportSelector Process Fail, Type [" + ImportSelector.class.getName()
                                + "] Class [" + importSelector.getClass().getName() + "].", e);
            }
        }
    }

    /**
     * 执行注解类处理逻辑
     * @param annotationClassMap 注解类map
     * @param beanAnnotationScanMetadataCollection Bean扫描元数据集合
     */
    private void doHandleAnnotationClass(Map<Class<? extends Annotation>, Collection<Class<?>>> annotationClassMap,
                                         Collection<BeanAnnotationScanMetadata> beanAnnotationScanMetadataCollection) {
        Map<Class<? extends Annotation>, Collection<Class<?>>> componentAnnotationClassMap = new HashMap<>(annotationClassMap.size());
        // 处理Import注解
        doHandleImportAnnotationClass(annotationClassMap, componentAnnotationClassMap, beanAnnotationScanMetadataCollection);

        // 处理Aspect注解
        Collection<Class<?>> aspectClasses = annotationClassMap.get(Aspect.class);
        if (aspectClasses != null) {
            AnnotationHandler aspectHandler = annotationHandlerMap.get(Aspect.class);
            if (aspectHandler != null) {
                aspectHandler.handle(aspectClasses, beanAnnotationScanMetadataCollection);
            }
            addComponentAnnotationClass(componentAnnotationClassMap, aspectClasses);
        }

        // 处理其他注解
        for (Map.Entry<Class<? extends Annotation>, Collection<Class<?>>> entry : annotationClassMap.entrySet()) {
            if (Aspect.class.equals(entry.getKey()) || Import.class.equals(entry.getKey())) {
                continue;
            }
            if (Component.class.equals(entry.getKey())) {
                addComponentAnnotationClass(componentAnnotationClassMap, entry.getValue());
                continue;
            }
            AnnotationHandler handler = annotationHandlerMap.get(entry.getKey());
            if (handler != null) {
                handler.handle(entry.getValue(), beanAnnotationScanMetadataCollection);
            }
        }
        if (!componentAnnotationClassMap.isEmpty()) {
            // 执行bean注册初始化后的扩展方法
            doAfterAnnotationBeanRegisterInitialize();
            AnnotationHandler componentHandler = annotationHandlerMap.get(Component.class);
            componentHandler.handle(componentAnnotationClassMap.get(Component.class), beanAnnotationScanMetadataCollection);
        }
    }

    /**
     * 处理Import注解
     * @param annotationClassMap 注解类map
     * @param componentAnnotationClassMap component注解类map
     * @param beanAnnotationScanMetadataCollection 所有Bean扫描元数据类型
     */
    private void doHandleImportAnnotationClass(Map<Class<? extends Annotation>, Collection<Class<?>>> annotationClassMap,
                                               Map<Class<? extends Annotation>, Collection<Class<?>>> componentAnnotationClassMap,
                                               Collection<BeanAnnotationScanMetadata> beanAnnotationScanMetadataCollection) {
        Collection<Class<?>> importClasses = annotationClassMap.get(Import.class);
        if (importClasses != null) {
            ImportAnnotationMetadata importAnnotationMetadata = new ImportAnnotationMetadata();
            for (Class<?> importClass : importClasses) {
                addComponentAnnotationClass(componentAnnotationClassMap, Collections.singleton(importClass));
                for (Stack<Class<? extends Annotation>> stack : BeanUtils.findReverseAllSpecifiedAnnotationStack(importClass, Import.class)) {
                    ImportAnnotationTypeMetadata importAnnotationTypeMetadata = new ImportAnnotationTypeMetadata();
                    importAnnotationTypeMetadata.setOriginalClass(importClass);
                    List<Class<? extends Annotation>> annotationList = new ArrayList<>(stack.size());
                    Class<?> clazz = importClass;
                    while (!stack.isEmpty()) {
                        if (Import.class.equals(stack.peek())) {
                            annotationList.add(stack.pop());
                            break;
                        } else {
                            Class<? extends Annotation> annotationClass = stack.pop();
                            clazz = annotationClass;
                            annotationList.add(annotationClass);
                        }
                    }
                    Import importType = clazz.getAnnotation(Import.class);
                    if (importType == null) {
                        continue;
                    }
                    importAnnotationTypeMetadata.setImportClass(clazz);
                    importAnnotationTypeMetadata.setImportAnnotation(importType);
                    for (Class<?> importClazz : importType.value()) {
                        if (isValidClass(importClazz)) {
                            annotationHandlerMap.keySet().stream()
                                    .filter(c -> BeanUtils.isExistSpecifiedAnnotation(importClazz, c))
                                    .findFirst()
                                    .ifPresent(c -> {
                                        annotationClassMap.get(c).add(importClazz);
                                        beanAnnotationScanMetadataCollection.addAll(createBeanAnnotationScanMetadataCollection(importClazz));
                                    });
                        }
                    }

                    Stack<Class<? extends Annotation>> reverseStack = new Stack<>();
                    for (int i = annotationList.size() - 1; i >= 0; i--) {
                        reverseStack.push(annotationList.get(i));
                    }

                    clazz = importAnnotationTypeMetadata.getOriginalClass();
                    List<ClassAndAnnotation> classAndAnnotationList = new ArrayList<>(reverseStack.size());
                    while (!reverseStack.isEmpty()) {
                        Class<? extends Annotation> annotationClass = reverseStack.pop();
                        ClassAndAnnotation classAndAnnotation = new ClassAndAnnotation();
                        classAndAnnotation.setClazz(clazz);
                        classAndAnnotation.setAnnotation(clazz.getAnnotation(annotationClass));
                        classAndAnnotationList.add(classAndAnnotation);
                        clazz = annotationClass;
                    }
                    importAnnotationTypeMetadata.setClassAndAnnotations(Collections.unmodifiableCollection(classAndAnnotationList));
                    importAnnotationMetadata.getMetadata().add(importAnnotationTypeMetadata);
                }
            }
            ((BaseApplicationContext) getNutApplication().getApplicationContext())
                    .registerBean(ReflectUtils.getDefaultBeanName(ImportAnnotationMetadata.class), importAnnotationMetadata);
        }
    }

    /**
     * 添加component注解class
     * @param map 缓存对象
     * @param addClasses 要添加的class对象
     */
    private void addComponentAnnotationClass(Map<Class<? extends Annotation>, Collection<Class<?>>> map, Collection<Class<?>> addClasses) {
        Collection<Class<?>> classes = map.get(Component.class);
        if (classes == null) {
            map.put(Component.class, new ArrayList<>(64));
            classes = map.get(Component.class);
        }
        classes.addAll(addClasses);
    }

    /**
     * 是否是有效的class对象
     * @param clazz class对象
     * @return class对象
     */
    private boolean isValidClass(Class<?> clazz) {
        int modifiers = clazz.getModifiers();
        return !Annotation.class.isAssignableFrom(clazz)
                && !Modifier.isInterface(modifiers)
                && !Modifier.isAbstract(modifiers)
                && !Modifier.isPrivate(modifiers);
    }

    /**
     * 获取Bean扫描元数据集合
     * @param annotationClassMap 注解处理器map
     * @return Bean扫描元数据集合
     */
    private Collection<BeanAnnotationScanMetadata> getBeanAnnotationScanMetadataCollection (
            Map<Class<? extends Annotation>, Collection<Class<?>>> annotationClassMap) {
        if (beanAnnotationScanMetadataCollection != null) {
            return beanAnnotationScanMetadataCollection;
        }
        List<BeanAnnotationScanMetadata> beanAnnotationScanMetadataList = new LinkedList<>();
        for (Map.Entry<Class<? extends Annotation>, Collection<Class<?>>> entry : annotationClassMap.entrySet()) {
            for (Class<?> clazz : entry.getValue()) {
                beanAnnotationScanMetadataList.addAll(createBeanAnnotationScanMetadataCollection(entry.getKey(), clazz));
            }
        }
        beanAnnotationScanMetadataCollection = beanAnnotationScanMetadataList;
        return beanAnnotationScanMetadataCollection;
    }

    /**
     * 创建Bean扫描元数据集合
     * @param annotationType 注解类型
     * @param clazz class对象
     * @return Bean扫描元数据集合
     */
    private Collection<BeanAnnotationScanMetadata> createBeanAnnotationScanMetadataCollection(Class<? extends Annotation> annotationType, Class<?> clazz) {
        List<BeanAnnotationScanMetadata> beanAnnotationScanMetadataList = new LinkedList<>();
        BeanAnnotationScanMetadata.Source source = getBeanAnnotationScanMetadataSource(annotationType);
        if (Configuration.class.equals(annotationType)) {
            for (Method method : ReflectUtils.getBeanAnnotationMethod(clazz, Bean.class)) {
                Bean annotation = method.getAnnotation(Bean.class);
                String beanName;
                if (StringUtils.isEmpty(annotation.value())) {
                    beanName = method.getName();
                } else {
                    beanName = annotation.value();
                }
                ConfigurationBeanAnnotationScanMetadata beanAnnotationScanMetadata =
                        new ConfigurationBeanAnnotationScanMetadata(beanName, method.getReturnType(), source,
                                method.isAnnotationPresent(Primary.class), annotationType,
                                annotationHandlerMap.get(annotationType), clazz, method);
                beanAnnotationScanMetadataList.add(beanAnnotationScanMetadata);
            }
        } else {
            String beanName = getBeanDefineName(clazz);
            BeanAnnotationScanMetadata beanAnnotationScanMetadata = new BeanAnnotationScanMetadata(beanName,
                    clazz, source, clazz.isAnnotationPresent(Primary.class), annotationType,
                    annotationHandlerMap.get(annotationType));
            beanAnnotationScanMetadataList.add(beanAnnotationScanMetadata);
        }
        return beanAnnotationScanMetadataList;
    }

    /**
     * 创建Bean扫描元数据集合
     * @param clazz class对象
     * @return Bean扫描元数据集合
     */
    private Collection<BeanAnnotationScanMetadata> createBeanAnnotationScanMetadataCollection(Class<?> clazz) {
        Class<? extends Annotation> annotationType = null;
        if (clazz.isAnnotationPresent(Component.class)) {
            annotationType = Component.class;
        } else if (clazz.isAnnotationPresent(Configuration.class)) {
            annotationType = Configuration.class;
        } else if (clazz.isAnnotationPresent(ConfigurationProperties.class)) {
            annotationType = ConfigurationProperties.class;
        } else if (clazz.isAnnotationPresent(Aspect.class)) {
            annotationType = Aspect.class;
        }
        return createBeanAnnotationScanMetadataCollection(annotationType, clazz);
    }

    /**
     * 获取Bean扫描元数据来源
     * @param annotationType 注解类型
     * @return Bean扫描元数据来源
     */
    private BeanAnnotationScanMetadata.Source getBeanAnnotationScanMetadataSource(Class<? extends Annotation> annotationType) {
        if (Component.class.equals(annotationType)) {
            return BeanAnnotationScanMetadata.Source.COMPONENT;
        } else if (Configuration.class.equals(annotationType)) {
            return BeanAnnotationScanMetadata.Source.CONFIGURATION;
        } else if (ConfigurationProperties.class.equals(annotationType)) {
            return BeanAnnotationScanMetadata.Source.CONFIGURATION_PROPERTIES;
        } else if (Aspect.class.equals(annotationType)) {
            return BeanAnnotationScanMetadata.Source.ASPECT;
        }
        return null;
    }

    /**
     * 是否是bean注解注解
     * @param annotationType 注解类型
     * @return 是否是bean注解注解
     */
    private boolean isBeanRegisterAnnotation(Class<? extends Annotation> annotationType) {
        return Component.class.equals(annotationType) || Configuration.class.equals(annotationType)
                || ConfigurationProperties.class.equals(annotationType) || Aspect.class.equals(annotationType)
                || Import.class.equals(annotationType);
    }

    /**
     * 获取bean的定义名称
     * @param clazz bean名称
     */
    private String getBeanDefineName(Class<?> clazz) {
        Component component = clazz.getAnnotation(Component.class);
        if (component != null && !StringUtils.isEmpty(component.value())) {
            return component.value();
        }
        Controller controller = clazz.getAnnotation(Controller.class);
        if (controller != null && !StringUtils.isEmpty(controller.value())) {
            return controller.value();
        }
        RestController restController = clazz.getAnnotation(RestController.class);
        if (restController != null && !StringUtils.isEmpty(restController.value())) {
            return restController.value();
        }
        return ReflectUtils.getDefaultBeanName(clazz);
    }

    /**
     * 执行bean定义后置处理方法
     */
    private void doExecuteBeanDefinitionPostProcess(Map<Class<? extends Annotation>, AnnotationHandler> annotationHandlerMap,
                                                    Map<Class<? extends Annotation>, Collection<Class<?>>> annotationClassMap) {
        Collection<BeanAnnotationScanMetadata> beanAnnotationScanMetadataCollection = getBeanAnnotationScanMetadataCollection(annotationClassMap);
        BeanDefinitionRegistry beanDefinitionRegistry = new DefaultAnnotationBeanDefinitionRegistry(annotationHandlerMap, annotationClassMap);
        annotationClassMap.entrySet().stream()
                .filter(t -> Component.class.equals(t.getKey()))
                .flatMap(e -> e.getValue().stream().filter(BeanDefinitionPostProcessor.class::isAssignableFrom)
                        .map(c -> new BeanDefinitionPostProcessorType(annotationHandlerMap.get(e.getKey()), c)))
                .map(t -> createBeanDefinitionPostProcessorInstance(t.annotationHandler, t.clazz, beanAnnotationScanMetadataCollection))
                .sorted(Comparator.comparing(BeanDefinitionPostProcessor::getOrder))
                .forEach(p -> {
                    try {
                        p.process(beanDefinitionRegistry);
                    } catch (Exception e) {
                        logger.logp(Level.SEVERE, p.getClass().getName(), "process",
                                "BeanDefinitionPostProcessor class [" + p.getClass().getName() + "] Execute Fail", e);
                    }
                });
    }

    /**
     * 创建bean定义后置处理器实例
     * @param clazz bean定义后置处理器类型
     * @return bean定义后置处理器实例
     */
    private BeanDefinitionPostProcessor createBeanDefinitionPostProcessorInstance(AnnotationHandler annotationHandler, Class<?> clazz,
                                                                                  Collection<BeanAnnotationScanMetadata> beanAnnotationScanMetadataCollection) {
        try {
            annotationHandler.handle(Collections.singleton(clazz), beanAnnotationScanMetadataCollection);
            try {
                Object bean = nutApplication.getApplicationContext().getBean(clazz);
                return (BeanDefinitionPostProcessor) bean;
            } catch (NoSuchBeanException e) {
                Constructor<?> constructor = clazz.getConstructor();
                return (BeanDefinitionPostProcessor) constructor.newInstance();
            }
        } catch (Exception e) {
            throw new BeanException("BeanDefinitionPostProcessor Instance Create Fail.", e);
        }
    }

    /**
     * bean定义后置处理器类型
     */
    private static class BeanDefinitionPostProcessorType {
        /** 注解处理器 */
        private final AnnotationHandler annotationHandler;
        /** 处理器类对象 */
        private final Class<?> clazz;

        /**
         * 构建bean定义后置处理器类型
         * @param annotationHandler 注解处理器
         * @param clazz 处理器类对象
         */
        private BeanDefinitionPostProcessorType(AnnotationHandler annotationHandler, Class<?> clazz) {
            this.annotationHandler = annotationHandler;
            this.clazz = clazz;
        }
    }
}
